package com.homedo.microservice.open.service.gateway.zuul.conf;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.homedo.common.util.Detect;
import com.homedo.microservice.open.service.gateway.bean.po.enhanced.RouteDomainEnhanced;
import com.homedo.microservice.open.service.gateway.bean.po.enhanced.RouteEnhanced;
import com.homedo.microservice.open.service.gateway.bean.po.enhanced.RouteServiceEnhanced;
import com.homedo.microservice.open.service.gateway.bean.po.enhanced.RouteServiceUrlEnhanced;
import com.homedo.microservice.open.service.gateway.util.ApplicationContextUtil;
import com.homedo.microservice.open.service.gateway.zuul.constant.ZuulConstant;
import com.homedo.microservice.open.service.gateway.zuul.routelocator.LocalRouteLocator;
import com.netflix.client.ClientFactory;
import com.netflix.config.ConcurrentCompositeConfiguration;
import com.netflix.config.DynamicIntProperty;
import com.netflix.config.DynamicPropertyFactory;
import com.netflix.config.DynamicStringProperty;
import com.netflix.loadbalancer.DynamicServerListLoadBalancer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

public class RouteConf {
    private static final Logger LOGGER = LoggerFactory.getLogger(RouteConf.class);


    private static RouteConf instance = new RouteConf();
    //正式生效的路由列表
    private static final AtomicReference<Map<String, RouteEnhanced>> confsRef = new AtomicReference<Map<String, RouteEnhanced>>(Maps.newConcurrentMap());
    //路由缓存列表，触发正式列表更新
    private static final AtomicReference<Map<String, RouteEnhanced>> confsTempRef = new AtomicReference<Map<String, RouteEnhanced>>(Maps.newConcurrentMap());

    private ZuulCommonConf zuulCommonConf;

    private volatile boolean running = true;

    private Thread checherThread = new Thread("RouteConf") {

        @Override
        public void run() {
            while (running) {
                try {
                    initSource();
                    if (zuulCommonConf == null) {
                        LOGGER.warn("[资源待初始化...]");
                        continue;
                    }

                    dealConfs();

                } catch (Throwable t) {
                    LOGGER.error("RouteDBConf run error!", t);
                } finally {
                    try {
                        sleep(zuulCommonConf.getRouteConfUpdateInterval().get());
                    } catch (InterruptedException e) {
                        LOGGER.error("RouteDBConf sleep error!", e);
                    }
                }
            }
        }
    };

    private void dealConfs() {
        if (!Detect.notEmpty(confsTempRef.get())) {
            return;
        }

        final boolean[] hasUpdate = {false};

        confsRef.get().entrySet().forEach(e -> {
            RouteEnhanced routeEnhanced = confsTempRef.get().get(e.getKey());
            if (routeEnhanced == null) {
                confsRef.get().remove(e.getKey());//缓存中不存在即移除
                hasUpdate[0] = true;
            }
        });

        confsTempRef.get().entrySet().forEach(e -> {
            RouteEnhanced routeEnhanced = confsRef.get().get(e.getKey());
            if (routeEnhanced == null || !routeEnhanced.equals(e.getValue())) {

                RouteEnhanced routeEnhancedCopy = new RouteEnhanced();
                BeanUtils.copyProperties(e.getValue(), routeEnhancedCopy);

                confsRef.get().put(e.getKey(), routeEnhancedCopy);

                hasUpdate[0] = true;
            }
        });

        if (hasUpdate[0]) {
            List<RouteEnhanced> routeEnhanceds = Lists.newArrayList();
            confsRef.get().entrySet().forEach(e -> {
                routeEnhanceds.add(e.getValue());
            });
            Collections.sort(routeEnhanceds);

            this.update(routeEnhanceds);
        }

    }

    private void update(List<RouteEnhanced> routeEnhanceds) {
        LocalRouteLocator discoveryClientRouteLocator = ApplicationContextUtil.getApplicationContext().getBean(LocalRouteLocator.class);
        discoveryClientRouteLocator.resetRoutes();

        routeEnhanceds.forEach(e -> {
            RouteServiceEnhanced routeServiceEnhanced = e.getRouteServiceEnhanced();
            RouteDomainEnhanced routeDomainEnhanced = e.getRouteDomainEnhanced();
            List<RouteServiceUrlEnhanced> routeServiceUrlEnhanceds = routeServiceEnhanced.getRouteServiceUrlEnhanceds();

            if (routeServiceEnhanced != null && Detect.notEmpty(routeServiceEnhanced.getId())) {

                String serviceId = routeServiceEnhanced.getId();
                String domain = "";
                String urls = "";

                if (routeDomainEnhanced != null && Detect.notEmpty(routeDomainEnhanced.getDomain())) {
                    domain = routeDomainEnhanced.getDomain();
                }

                if (Detect.notEmpty(routeServiceUrlEnhanceds)) {
                    for (int i = 0; i < routeServiceUrlEnhanceds.size(); i++) {
                        if (i < routeServiceUrlEnhanceds.size() - 1) {
                            urls += routeServiceUrlEnhanceds.get(i).getUrl() + ",";
                        } else {
                            urls += routeServiceUrlEnhanceds.get(i).getUrl();
                        }
                    }
                }

                this.initServiceHystrixCommandPropties(serviceId, urls);

                ClientFactory.getNamedClient(serviceId);

                DynamicServerListLoadBalancer serverListLoadBalancer = (DynamicServerListLoadBalancer) ClientFactory.getNamedLoadBalancer(serviceId);

                ZuulProperties.ZuulRoute route = new ZuulProperties.ZuulRoute();
                Set<String> sensitiveHeaders = Sets.newHashSet();
                if (Detect.notEmpty(e.getSensitiveHeaders())) {
                    for (String sensitiveHeader : e.getSensitiveHeaders().split(",")) {
                        sensitiveHeaders.add(sensitiveHeader);
                    }
                }

                route.setId(e.getId());
                route.setPath(this.getPath(domain, e.getPath()));
                route.setServiceId(serviceId);
                route.setStripPrefix(e.getStripPrefix());
                route.setRetryable(e.getRetryable());
                route.setCustomSensitiveHeaders(e.getCustomSensitiveHeaders());
                route.setSensitiveHeaders(sensitiveHeaders);

                discoveryClientRouteLocator.addRoute(route);
            }

        });

        ApplicationContextUtil.getApplicationContext().publishEvent(new RoutesRefreshedEvent(discoveryClientRouteLocator));

    }

    private void initServiceHystrixCommandPropties(String serviceId, String urls) {
        ConcurrentCompositeConfiguration myConfiguration =
                (ConcurrentCompositeConfiguration) DynamicPropertyFactory.getInstance().getBackingConfigurationSource();
        myConfiguration.setOverrideProperty(serviceId + ".ribbon.listOfServers", urls);
        myConfiguration.setOverrideProperty(serviceId + ".ribbon.NFLoadBalancerRuleClassName", "com.netflix.loadbalancer.ZoneAvoidanceRule");

        DynamicIntProperty timeoutInMilliseconds = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds", 14000);
        DynamicStringProperty strategy = DynamicPropertyFactory.getInstance().getStringProperty("hystrix.command.default.execution.isolation.strategy", "SEMAPHORE");
        DynamicIntProperty maxConcurrentRequests = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests", 1000000);
        DynamicIntProperty fallbackMaxConcurrentRequests = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests", 1000000);
        DynamicIntProperty requestVolumeThreshold = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.command.default.circuitBreaker.requestVolumeThreshold", 20);
        DynamicIntProperty sleepWindowInMilliseconds = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds", 5000);
        DynamicStringProperty errorThresholdPercentage = DynamicPropertyFactory.getInstance().getStringProperty("hystrix.command.default.circuitBreaker.errorThresholdPercentage", "95%");
        DynamicIntProperty timeInMilliseconds = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.command.default.metrics.rollingStats.timeInMilliseconds", 10000);
        DynamicIntProperty numBuckets = DynamicPropertyFactory.getInstance().getIntProperty("hystrix.command.default.metrics.rollingStats.numBuckets", 10);

        myConfiguration.setOverrideProperty("hystrix.command." + serviceId + ".execution.isolation.thread.timeoutInMilliseconds", timeoutInMilliseconds.get());
        myConfiguration.setOverrideProperty("hystrix.command." + serviceId + ".execution.isolation.strategy", strategy.get());
        myConfiguration.setOverrideProperty("hystrix.command." + serviceId + ".execution.isolation.semaphore.maxConcurrentRequests", maxConcurrentRequests.get());
        myConfiguration.setOverrideProperty("hystrix.command." + serviceId + ".fallback.isolation.semaphore.maxConcurrentRequests", fallbackMaxConcurrentRequests.get());
        myConfiguration.setOverrideProperty("hystrix.command." + serviceId + ".circuitBreaker.requestVolumeThreshold", requestVolumeThreshold.get());
        myConfiguration.setOverrideProperty("hystrix.command." + serviceId + ".circuitBreaker.sleepWindowInMilliseconds", sleepWindowInMilliseconds.get());
        myConfiguration.setOverrideProperty("hystrix.command." + serviceId + ".circuitBreaker.errorThresholdPercentage", errorThresholdPercentage.get());
        myConfiguration.setOverrideProperty("hystrix.command." + serviceId + ".metrics.rollingStats.timeInMilliseconds", timeInMilliseconds.get());
        myConfiguration.setOverrideProperty("hystrix.command." + serviceId + ".metrics.rollingStats.numBuckets", numBuckets.get());

    }

    private String getPath(String domain, String path) {
        if (!Detect.notEmpty(domain)) {
            return path;
        }
        String adjustPath = this.adjustPath(path);
        String split = ZuulConstant.ZUUL_ROUTE_ADD_PREFIX_SPLIT_PATH;
        if (adjustPath.indexOf(split) > -1) {
            split = ZuulConstant.ZUUL_ROUTE_ADD_PREFIX_SPLIT;
        }
        if ("**".equals(adjustPath)) {
            adjustPath = "**/**";
        }
        return "/" + domain + split + adjustPath;
    }

    private String adjustPath(String path) {
        if (path.startsWith("/")) {
            return this.adjustPath(path.substring(1));
        }
        return path;
    }

    private void initSource() {
        if (zuulCommonConf == null) {
            synchronized (RouteConf.class) {
                if (zuulCommonConf == null) {
                    zuulCommonConf = ZuulCommonConf.get();
                }
            }
        }
    }


    public static void start() {
        getInstance().checherThread.setDaemon(true);
        getInstance().checherThread.start();
    }

    public static void stop() {
        getInstance().running = false;
    }


    private RouteConf() {
    }

    public static RouteConf getInstance() {
        return instance;
    }

    public Map<String, RouteEnhanced> getConfs() {
        return confsRef.get();
    }

    public Map<String, RouteEnhanced> getConfsTemp() {
        return confsTempRef.get();
    }

}
